package com.informatics.polymer.server.Polymerisation;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import nu.xom.Builder;
import nu.xom.Document;
import nu.xom.ParsingException;
import nu.xom.ValidityException;

import org.xmlcml.cml.element.CMLAtom;
import org.xmlcml.cml.element.CMLBond;
import org.xmlcml.cml.element.CMLBondArray;
import org.xmlcml.cml.element.CMLFragment;
import org.xmlcml.cml.element.CMLJoin;
import org.xmlcml.cml.element.CMLMolecule;

/**
 * Class to create graft copolymers.<br>
 * </br> <b>Example</b><br>
 * </br> GraftCopolymerisation graftCopolymerisation = new
 * GraftCopolymerisation(
 * 4,1,"Br[R]","[R]CCCC([R])CC([R])CC[R]","[R]C(F)C(F)CC[R]","Cl[R]");<br>
 * </br> String graftCopolymer2D = graftCopolymerisation.getPolymer().toXML();<br>
 * </br> graftCopolymerisation.add3DCoordinates(graftCopolymer);<br>
 * </br> <b>Note</b><br>
 * </br> Grafted chains in this addition will only have 2 R-groups.<br>
 * </br> In the first instance we will just deal with the use of a homopolymer
 * back bone with other homopolymers bound to it.<br>
 * </br> Also note that R-groups will have to be predefined by the user on the
 * main chain.<br>
 * </br> Coordinate generation is done using external dependencies, from the
 * Chemistry Development kit.<br>
 * </br> <a href="http://pele.farmbio.uu.se/nightly/api/org/openscience/cdk/modeling/builder3d/ModelBuilder3D.html"
 * >ModelBuilder3D class</a>.
 * 
 * @author Ed Cannon
 * @version 1.0
 */
public class GraftCopolymerisation implements Polymerisation {
	/**
	 * Integer representing the degree of polymerisation of the main chain.
	 */
	private int degreeOfPolymerisationMainChain;
	/**
	 * Integer representing the degree of polymerisation of the branch chain.
	 */
	private int degreeOfPolymerisationBranchChain;
	/**
	 * CMLMolecule representing end group 1.
	 */
	private CMLMolecule endGroup1;
	/**
	 * CMLMolecule representing the main chain.
	 */
	private CMLMolecule mainChain;
	/**
	 * CMLMolecule representing the branch chain.
	 */
	private CMLMolecule branchChain;
	/**
	 * CMLMolecule representing end group 2.
	 */
	private CMLMolecule endGroup2;
	/**
	 * CMLMolecule representing the graft copolymer.
	 */
	private CMLMolecule graftCopolymer;
	/**
	 * Constructs an object to undertake GraftCopolymerisation.
	 */
	public GraftCopolymerisation() {
	}

	/**
	 * Constructs an object to undertake GraftCopolymerisation, using the degree
	 * of polymerisation of the main chain, degree of polymerisation of the
	 * branch chain, and a string representation for the end groups and
	 * main/branch chain.
	 * 
	 * @param degreeOfPolymerisationMainChainValue
	 *            - int representing the number of main chain repeat unit
	 *            molecules joined together.
	 * @param degreeOfPolymerisationBranchChainValue
	 *            - int representing the number of branch chain repeat unit
	 *            molecules joined together.
	 * @param eg1
	 *            - SMILE String representation of end group 1.
	 * @param mc
	 *            - SMILE String representation of the main chain.
	 * @param bc
	 *            - SMILE String representation of the branch chain.
	 * @param eg2
	 *            - SMILE String representation of end group 2.
	 */
	public GraftCopolymerisation(
			final int degreeOfPolymerisationMainChainValue,
			final int degreeOfPolymerisationBranchChainValue, final String eg1,
			final String mc, final String bc, final String eg2) {
		if (degreeOfPolymerisationMainChainValue < 1) {
			throw new IllegalArgumentException(
					"degreeOfPolymerisationMainChainValue <1!");
		} else {
			this.degreeOfPolymerisationMainChain = degreeOfPolymerisationMainChainValue;
		}
		if (degreeOfPolymerisationBranchChainValue < 1) {
			throw new IllegalArgumentException(
					"degreeOfPolymerisationBranchChainValue <1!");
		} else {
			this.degreeOfPolymerisationBranchChain = degreeOfPolymerisationBranchChainValue;
		}
		if (eg1 == null) {
			this.endGroup1 = PolymerisationUtils.convertSmileString2CML(
					"[R][H]", "Eg1");
		} else {
			int rCount = PolymerisationUtils.countNumberofFragmentPoints(eg1,
					"[R]");
			if (rCount == 1) {
				this.endGroup1 = PolymerisationUtils.convertSmileString2CML(
						eg1, "Eg1");
			} else if (rCount != 1) {
				throw new IllegalArgumentException(
						"End groups are only allowed one R group!");
			}
		}
		if (mc == null) {
			throw new NullPointerException(
					"No repeat unit entered, please enter a repeat unit smile string");
		} else {
			int rCount = PolymerisationUtils.countNumberofFragmentPoints(mc,
					"[R]");
			if (rCount > 2) {
				this.mainChain = PolymerisationUtils.convertSmileString2CML(mc,
						"");
			} else if (rCount == 2) {
				this.mainChain = PolymerisationUtils.convertSmileString2CML(mc,
						"");
				// this.branchChain =
				// PolymerisationUtils.convertSmileString2CML("[R][H]", "Eg2");
			} else if (rCount < 2) {
				throw new IllegalArgumentException(
						"Main chain must have at least two R-groups!");
			}
		}
		if (bc == null
				|| PolymerisationUtils.countNumberofFragmentPoints(bc, "[R]") < 1) {
			this.branchChain = PolymerisationUtils.convertSmileString2CML(
					"[R][H]", "BC");
		} else {
			this.branchChain = PolymerisationUtils.convertSmileString2CML(bc,
					"");
		}

		if (eg2 == null) {
			this.endGroup2 = PolymerisationUtils.convertSmileString2CML(
					"[R][H]", "Eg2");
		} else {
			int rCount = PolymerisationUtils.countNumberofFragmentPoints(eg2,
					"[R]");
			if (rCount == 1) {
				this.endGroup2 = PolymerisationUtils.convertSmileString2CML(
						eg2, "Eg2");
			} else if (rCount != 1) {
				throw new IllegalArgumentException(
						"End groups are only allowed one R group!");
			}
		}
	}

	/**
	 * Constructs an object to undertake GraftCopolymerisation, using the degree
	 * of polymerisation of the main chain, degree of polymerisation of the
	 * branch chain, and a CMLMolecule representation for the end groups and
	 * main/branch chain.
	 * 
	 * @param degreeOfPolymerisationMainChainValue
	 *            - int representing the number of main chain repeat unit
	 *            molecules joined together.
	 * @param degreeOfPolymerisationBranchChainValue
	 *            - int representing the number of branch chain repeat unit
	 *            molecules joined together.
	 * @param endGroup1Value
	 *            - CMLMolecule representation of end group 1.
	 * @param mainChainValue
	 *            - CMLMolecule representation of the main chain.
	 * @param branchChainValue
	 *            - CMLMolecule representation of the branch chain.
	 * @param endGroup2Value
	 *            - CMLMolecule representation of end group 2.
	 */
	public GraftCopolymerisation(
			final int degreeOfPolymerisationMainChainValue,
			final int degreeOfPolymerisationBranchChainValue,
			final CMLMolecule endGroup1Value, final CMLMolecule mainChainValue,
			final CMLMolecule branchChainValue, final CMLMolecule endGroup2Value) {
		if (degreeOfPolymerisationMainChainValue < 1) {
			throw new IllegalArgumentException(
					"degreeOfPolymerisationMainChainValue <1!");
		} else {
			this.degreeOfPolymerisationMainChain = degreeOfPolymerisationMainChainValue;
		}
		if (degreeOfPolymerisationBranchChainValue < 1) {
			throw new IllegalArgumentException(
					"degreeOfPolymerisationBranchChainValue <1!");
		} else {
			this.degreeOfPolymerisationBranchChain = degreeOfPolymerisationBranchChainValue;
		}
		if (endGroup1Value == null) {
			this.endGroup1 = PolymerisationUtils.convertSmileString2CML(
					"[R][H]", "Eg1");
		} else {
			if (PolymerisationUtils.getRElements(endGroup1Value).size() == 1) {
				this.endGroup1 = PolymerisationUtils.addId(endGroup1Value,
						"Eg1");
			} else {
				throw new IllegalArgumentException(
						"End groups need to have 1 R group!");
			}
		}
		this.mainChain = mainChainValue;
		if (mainChainValue == null) {
			throw new NullPointerException(
					"No main chain entered, please enter a main chain");
		} else {
			if (PolymerisationUtils.getRElements(mainChainValue).size() >= 2) {
				this.mainChain = PolymerisationUtils.addId(mainChainValue, "");
			} else {
				throw new IllegalArgumentException(
						"Repeat unit needs at least two R groups!");
			}
		}
		if (branchChainValue == null
				|| PolymerisationUtils.getRElements(branchChainValue).size() < 1) {
			this.branchChain = PolymerisationUtils.convertSmileString2CML(
					"[R][H]", "BC");
		} else {
			this.branchChain = branchChainValue;
		}
		if (endGroup2Value == null) {
			this.endGroup2 = PolymerisationUtils.convertSmileString2CML(
					"[R][H]", "Eg2");
		} else {
			if (PolymerisationUtils.getRElements(endGroup2Value).size() == 1) {
				this.endGroup2 = PolymerisationUtils.addId(endGroup2Value,
						"Eg2");
			} else {
				throw new IllegalArgumentException(
						"End groups need to have 1 R group!");
			}
		}
	}

	/**
	 * Sets degree of polymerisation of the main chain.
	 * 
	 * @param degreeOfPoly
	 *            - int representing the number of repeat units of the main
	 *            chain
	 */
	public final void setMainChainDegreeOfPolymerisation(final int degreeOfPoly) {
		degreeOfPolymerisationMainChain = degreeOfPoly;
	}

	/**
	 * Sets the degree of polymerisation of the branch chain.
	 * 
	 * @param branchChainDegree
	 *            - int representing the number of repeat units of the branch
	 *            chain
	 */
	public final void setBranchChainDegreeOfPolymerisation(
			final int branchChainDegree) {
		degreeOfPolymerisationBranchChain = branchChainDegree;
	}

	/**
	 * Sets the CMLMolecule to represent end group 1 (optional), if not set,
	 * defaults to [R][H].
	 * 
	 * @param eg1
	 *            - CMLMolecule representation for end group 1
	 */
	public final void setEndGroup1(final CMLMolecule eg1) {
		endGroup1 = eg1;
	}

	/**
	 * Sets the CMLMolecule to represent end group 2 (optional), if not set,
	 * defaults to [R][H].
	 * 
	 * @param eg2
	 *            - CMLMolecule representation for end group 2
	 */
	public final void setEndGroup2(final CMLMolecule eg2) {
		endGroup2 = eg2;
	}

	/**
	 * Sets the CMLMolecule to represent the main chain (compulsory).
	 * 
	 * @param mainChainValue
	 *            - CMLMolecule representation of the main chain, main chains
	 *            with more than 2 R groups will have branch chains grafted onto
	 *            them. Main chains with 2R groups are essentially homopolymers.
	 */
	public final void setMainChain(final CMLMolecule mainChainValue) {
		mainChain = mainChainValue;
	}

	/**
	 * Sets the CMLMolecule to represent the branch chain (optional), if not
	 * set, defaults to [R][H].
	 * 
	 * @param branchChainValue
	 *            - CMLMolecule representation of the branch chain.
	 */
	public final void setBranchChain(final CMLMolecule branchChainValue) {
		branchChain = branchChainValue;
	}

	/**
	 * Returns int representing the number of main chain repeat unit molecules
	 * joined together.
	 * 
	 * @return degreeOfPolymerisationMainChain
	 */
	public final int getMainChainDegreeOfPolymerisation() {
		return degreeOfPolymerisationMainChain;
	}

	/**
	 * Returns int representing the number of branch chain repeat unit molecules
	 * joined together.
	 * 
	 * @return degreeOfPolymerisationBranchChain
	 */
	public final int getBranchChainDegreeOfPolymerisation() {
		return degreeOfPolymerisationBranchChain;
	}

	/**
	 * Returns CMLMolecule representation for end group 1.
	 * 
	 * @return endGroup1
	 */
	public final CMLMolecule getEndGroup1() {
		return endGroup1;
	}

	/**
	 * Returns CMLMolecule representation for end group 2.
	 * 
	 * @return endGroup2
	 */
	public final CMLMolecule getEndGroup2() {
		return endGroup2;
	}

	/**
	 * Returns CMLMolecule representation for the main chain.
	 * 
	 * @return mainChain
	 */
	public final CMLMolecule getMainChain() {
		return mainChain;
	}

	/**
	 * Returns CMLMolecule representation for branch chain.
	 * 
	 * @return branchChain
	 */
	public final CMLMolecule getBranchChain() {
		return branchChain;
	}

	/**
	 * Returns an XML string representation of the generated alternating
	 * copolymer in 2D.
	 * 
	 * @return graftCopolymer.toXML() (non-Javadoc)
	 * @see org.xmlcml.cml.tools.polymer.Polymerisation#generatePML()
	 */
	public final String generatePML() {
		return graftCopolymer.toXML();
	}

	/**
	 * Takes an ArrayList<CMLMolecule> that represents the main chain with
	 * branches and two CMLMolecules to represent end groups, and joins the
	 * CMLMolecules using R-group connector points.
	 * 
	 * @param molecules
	 *            - ArrayList<CMLMolecule>, list of separate repeat unit
	 *            molecules to be joined
	 * @param eg1
	 *            - CMLMolecule representation of end group 1
	 * @param eg2
	 *            - CMLMolecule representation of end group 2
	 */
	private void joinUpMainChain(final ArrayList<CMLMolecule> molecules,
			final CMLMolecule eg1, final CMLMolecule eg2) {
		ArrayList<ArrayList<String>> rgroups = PolymerisationUtils
				.getRgroupConnectors(molecules);
		// In the first instance join up separate molecules
		graftCopolymer = new CMLMolecule();
		ArrayList<CMLAtom> eg1R = PolymerisationUtils.getRElements(eg1);
		ArrayList<CMLAtom> eg2R = PolymerisationUtils.getRElements(eg2);
		if (rgroups.size() == 1) {
			addAtomsAndBonds(eg1, eg2);
			// Add bonds from RU
			for (int i = 0; i < molecules.get(0).getAtomCount(); i++) {
				CMLAtom a = new CMLAtom();
				a.setAttribute("id", molecules.get(0).getAtom(i).getId());
				a.setAttribute("elementType", molecules.get(0).getAtom(i)
						.getElementType());
				a
						.setFormalCharge(molecules.get(0).getAtom(i)
								.getFormalCharge());
				// Add aromaticity flag
				if (molecules.get(0).getAtom(i).getAttribute("aromatic") != null) {
					a.setAttribute("aromatic", molecules.get(0).getAtom(i)
							.getAttributeValue("aromatic"));
				}
				graftCopolymer.addAtom(a);
			}
			PolymerisationUtils.addBonds(molecules, 0, graftCopolymer);
			CMLBond cb = new CMLBond();
			cb.setAttribute("atomRefs2", eg1R.get(0).getId() + " "
					+ rgroups.get(0).get(0));
			cb.setId(eg1R.get(0).getId() + " " + rgroups.get(0).get(0));
			cb.setOrder("1");
			graftCopolymer.addBond(cb);

			CMLBond cb2 = new CMLBond();
			cb2.setAttribute("atomRefs2", rgroups.get(0).get(
					rgroups.get(0).size() - 1)
					+ " " + eg2R.get(0).getId());
			cb2.setId(rgroups.get(0).get(rgroups.get(0).size() - 1) + " "
					+ eg2R.get(0).getId());
			cb2.setOrder("1");
			graftCopolymer.addBond(cb2);
			// Need to eliminate R-R bonds
			// Add R-R delete method
			addX1X2BondsDeleteRRBonds();
			// Now need to bind the graft chains on.
		} else {
			addAtomsAndBonds(eg1, eg2);
			// loop through the repeat unit
			for (int i = 0; i < molecules.size(); i++) {
				PolymerisationUtils.addAtoms(molecules, i, graftCopolymer);
				PolymerisationUtils.addBonds(molecules, i, graftCopolymer);
			}
			ArrayList<String> rrBondJoinIds = new ArrayList<String>();
			rrBondJoinIds.add(eg1R.get(0).getId());
			for (int i = 0; i < rgroups.size(); i++) {
				for (int j = 0; j < rgroups.get(i).size(); j++) {
					if ((j == 0) || (j == rgroups.get(i).size() - 1)) {
						rrBondJoinIds.add(rgroups.get(i).get(j));
					}

				}
			}
			rrBondJoinIds.add(eg2R.get(0).getId());
			for (int i = 0; i < rrBondJoinIds.size(); i++) {
				if (i % 2 == 0) {
					CMLBond cb = new CMLBond();
					cb.setAttribute("atomRefs2", rrBondJoinIds.get(i) + " "
							+ rrBondJoinIds.get(i + 1));
					cb.setId(rrBondJoinIds.get(i) + " "
							+ rrBondJoinIds.get(i + 1));
					cb.setOrder("1");
					graftCopolymer.addBond(cb);
				}
			}
			// Remove the R-R bonds
			// Add R-R delete method
			addX1X2BondsDeleteRRBonds();
		}
	}

	/**
	 * Polymerises branch chains to the main chain.
	 * 
	 * @param moleculeList
	 *            - ArrayList<CMLMolecule> to be added to form branches
	 * @return CMLMolecule. Method accepts an ArrayList<CMLMolecule>, and adds
	 *         the branch chain to the main chain
	 */
	private CMLMolecule polymeriseGraftChains(
			final ArrayList<CMLMolecule> moleculeList) {
		CMLMolecule newMolecule = new CMLMolecule();
		for (int i = 0; i < moleculeList.size(); i++) {
			// Add the atoms
			for (int j = 0; j < moleculeList.get(i).getAtomCount(); j++) {
				CMLAtom a = new CMLAtom();
				a.setAttribute("id", moleculeList.get(i).getAtom(j).getId());
				a.setAttribute("elementType", moleculeList.get(i).getAtom(j)
						.getElementType());
				a.setFormalCharge(moleculeList.get(i).getAtom(j)
						.getFormalCharge());
				// Add aromaticity flag
				if (moleculeList.get(i).getAtom(j).getAttribute("aromatic") != null) {
					a.setAttribute("aromatic", moleculeList.get(i).getAtom(j)
							.getAttributeValue("aromatic"));
				}
				newMolecule.addAtom(a);
			}
			// Now add the bonds
			PolymerisationUtils.addBonds(moleculeList, i, newMolecule);
		}
		ArrayList<ArrayList<String>> rgroupsvaluesBranch = PolymerisationUtils
				.getRgroupConnectors(moleculeList);
		ArrayList<String> rgroupIds = new ArrayList<String>();
		for (int i = 0; i < rgroupsvaluesBranch.size(); i++) {
			for (int j = 0; j < rgroupsvaluesBranch.get(i).size(); j++) {
				rgroupIds.add(rgroupsvaluesBranch.get(i).get(j));
			}
		}
		for (int i = 1; i < rgroupIds.size() - 1; i++) {
			if (i % 2 != 0) {
				CMLBond cb = new CMLBond();
				cb.setAttribute("atomRefs2", rgroupIds.get(i) + " "
						+ rgroupIds.get(i + 1));
				cb.setId(rgroupIds.get(i) + " " + rgroupIds.get(i + 1));
				cb.setOrder("1");
				newMolecule.addBond(cb);
			}
		}
		// Remove R-R bonds
		ArrayList<CMLBond> bothRAtoms = new ArrayList<CMLBond>();
		CMLBondArray rests = newMolecule.getBondArray();

		for (int i = 0; i < rests.getBonds().size(); i++) {
			if ("R".equals(rests.getBonds().get(i).getAtom(0).getElementType())
					&& "R".equals(rests.getBonds().get(i).getAtom(1)
							.getElementType())) {
				bothRAtoms.add(rests.getBonds().get(i));
			}
		}
		for (int i = 0; i < bothRAtoms.size(); i++) {
			if (i == 0) {
				RRBonds rrval = new RRBonds(bothRAtoms.get(i), newMolecule);
				rrval.automaticallySetX1X2();
				rrval.createX1X2Bond();
				rrval.deleteRgroupsAndBonds();
				newMolecule = rrval.getUpdatedMolecule();
			} else {
				RRBonds rrval = new RRBonds(bothRAtoms.get(i), newMolecule);
				rrval.automaticallySetX1X2();
				rrval.createX1X2Bond();
				rrval.deleteRgroupsAndBonds();
				newMolecule = rrval.getUpdatedMolecule();
			}
		}
		return newMolecule;
	}

	/**
	 * Method to add 3D coordinates given a pre-built 2D XML representation of
	 * the polymer.
	 * 
	 * @param pmlStringNoCoordinates
	 *            - 2D XML representation of the built polymer
	 * @return XML string representation of the polymer with 3D coordinates.
	 *         Coordinate generation is done using an external dependencies,
	 *         from the Chemistry Development kit ModelBuilder3D class @link
	 *         (http
	 *         ://pele.farmbio.uu.se/nightly/api/org/openscience/cdk/modeling
	 *         /builder3d/ModelBuilder3D.html).
	 * @throws Exception
	 *             if 3D coordinate generation fails.
	 */
	public final String add3DCoordinates(final String pmlStringNoCoordinates)
			throws Exception {
		String molWithCoords = PolymerisationUtils
				.add3DCoordinates(pmlStringNoCoordinates);
		return molWithCoords;
	}

	
	 /* public static void main(String[] args) throws Exception {
	  GraftCopolymerisation graftCopolymerisation = new
	  GraftCopolymerisation(4,2,"Br[R]","[R]CCCC([R])CC([R])CC[R]","[R]C(F)C(F)CC[R]","Cl[R]");
	  CMLFragment frag = graftCopolymerisation.getFragment();
	  Document pmlBasicDoc =  PolymerisationUtils.createPMLBasic(frag);
	  Document pmlIntermediateDoc = PolymerisationUtils.createPMLIntermediate(frag);
	  System.out.println(pmlBasicDoc.toXML());
	  System.out.println(pmlIntermediateDoc.toXML());
	  }
	  /* Document pmlBasicDoc = graftCopolymerisation.createPMLBasic(); Document
	 * pmlIntermediateDoc = graftCopolymerisation.createPMLIntermediate();
	 * System.out.println(pmlBasicDoc.toXML());
	 * System.out.println(pmlIntermediateDoc.toXML()); //String result =
	 * graftCopolymerisation.getPolymer().toXML();
	 * }//System.out.println(result); /
	 * //graftCopolymerisation.add3DCoordinates(result); }
	 */
	/**
	 * Return CMLMolecule representation of the built polymer.
	 * 
	 * @return graftCopolymer (non-Javadoc)
	 * @see org.xmlcml.cml.tools.polymer.Polymerisation#getPolymer()
	 */
	public final CMLMolecule getPolymer() {
	//	boolean buildable = PolymerisationUtils.isBuildable(getFragment());
		boolean buildable = true;
		if (buildable == true) {
			ArrayList<CMLMolecule> separateMainChainMolecules = new ArrayList<CMLMolecule>();
			separateMainChainMolecules = PolymerisationUtils
					.getRepeatUnitMolecules(
							this.degreeOfPolymerisationMainChain, mainChain,
							"MC");
			ArrayList<CMLMolecule> polymerisedBranchChain = PolymerisationUtils
					.getRepeatUnitMolecules(
							this.degreeOfPolymerisationBranchChain,
							branchChain, "BR");
			joinUpMainChain(separateMainChainMolecules, endGroup1, endGroup2);
			CMLMolecule polymerisedBranch = polymeriseGraftChains(polymerisedBranchChain);
			ArrayList<CMLAtom> finalGraftBackboneR = PolymerisationUtils
					.getRElements(graftCopolymer);
			ArrayList<CMLMolecule> allGraftsToMainChain = PolymerisationUtils
					.getRepeatUnitMolecules(finalGraftBackboneR.size(),
							polymerisedBranch, "Graft");
			ArrayList<ArrayList<String>> allRgroupsFromGraft = PolymerisationUtils
					.getRgroupConnectors(allGraftsToMainChain);
			// add all atoms and bonds to graftCopolymer from
			// allGraftsToMainChain
			for (int i = 0; i < allGraftsToMainChain.size(); i++) {
				for (int j = 0; j < allGraftsToMainChain.get(i).getAtomCount(); j++) {
					CMLAtom a = new CMLAtom();
					a.setAttribute("id", allGraftsToMainChain.get(i).getAtom(j)
							.getId());
					a.setAttribute("elementType", allGraftsToMainChain.get(i)
							.getAtom(j).getElementType());
					a.setFormalCharge(allGraftsToMainChain.get(i).getAtom(j)
							.getFormalCharge());
					// Add aromaticity flag
					if (allGraftsToMainChain.get(i).getAtom(j).getAttribute(
							"aromatic") != null) {
						a.setAttribute("aromatic", allGraftsToMainChain.get(i)
								.getAtom(j).getAttributeValue("aromatic"));
					}
					graftCopolymer.addAtom(a);
				}
				for (int j = 0; j < allGraftsToMainChain.get(i).getBondCount(); j++) {
					CMLBond b = new CMLBond();
					String id1 = allGraftsToMainChain.get(i).getBonds().get(j)
							.getAtomId(0);
					String id2 = allGraftsToMainChain.get(i).getBonds().get(j)
							.getAtomId(1);
					b.setAttribute("atomRefs2", id1 + " " + id2);
					b.setId(id1 + " " + id2);
					b.setOrder(allGraftsToMainChain.get(i).getBonds().get(j)
							.getOrder());
					graftCopolymer.addBond(b);
				}
			}
			// Add R-R bonds
			for (int i = 0; i < finalGraftBackboneR.size(); i++) {
				CMLBond cb = new CMLBond();
				String id1 = finalGraftBackboneR.get(i).getId();
				String id2 = allRgroupsFromGraft.get(i).get(0);
				cb.setAttribute("atomRefs2", id1 + " " + id2);
				cb.setId(id1 + " " + id2);
				cb.setOrder("1");
				graftCopolymer.addBond(cb);
			}
			// Remove R-R bonds
			addX1X2BondsDeleteRRBonds();
			PolymerisationUtils.replaceRWithH(graftCopolymer);
			return graftCopolymer;
		} else {
			UnbuildableException unbuildableException = new UnbuildableException(
					"Can't be built");
			unbuildableException.getMessage();
			return null;
		}
		// return this.graftCopolymer;
	}

	/**
	 * Method returns a fully atomistic CML Document representation of the
	 * polymer.
	 * 
	 * @return Document
	 */
	public final Document createCML() {
		Builder builder = new Builder();
		Document doc = null;
		try {
			doc = builder.build(graftCopolymer.toXML(), "");
		} catch (ValidityException e) {
			e.printStackTrace();
		} catch (ParsingException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		return doc;
	}

	/**
	 * Adds atoms and bonds from the end groups.
	 * 
	 * @param eg1
	 *            - CMLMolecule representation of end group 1.
	 * @param eg2
	 *            - CMLMolecule representation of end group 2.
	 */
	private void addAtomsAndBonds(final CMLMolecule eg1, final CMLMolecule eg2) {
		for (int j = 0; j < eg1.getAtomCount(); j++) {
			CMLAtom a = new CMLAtom();
			a.setAttribute("id", eg1.getAtom(j).getId());
			a.setAttribute("elementType", eg1.getAtom(j).getElementType());
			a.setFormalCharge(eg1.getAtom(j).getFormalCharge());
			// Add aromaticity flag
			if (eg1.getAtom(j).getAttribute("aromatic") != null) {
				a.setAttribute("aromatic", eg1.getAtom(j).getAttributeValue(
						"aromatic"));
			}
			graftCopolymer.addAtom(a);
		}
		List<CMLBond> eg1bonds = eg1.getBonds();
		for (int j = 0; j < eg1bonds.size(); j++) {
			CMLBond b = new CMLBond();
			b.setAttribute("atomRefs2", eg1bonds.get(j).getAtomId(0) + " "
					+ eg1bonds.get(j).getAtomId(1));
			b.setId(eg1bonds.get(j).getAtomId(0) + " "
					+ eg1bonds.get(j).getAtomId(1));
			b.setOrder("1");
			graftCopolymer.addBond(b);
		}
		for (int j = 0; j < eg2.getAtomCount(); j++) {
			CMLAtom a = new CMLAtom();
			a.setAttribute("id", eg2.getAtom(j).getId());
			a.setAttribute("elementType", eg2.getAtom(j).getElementType());
			a.setFormalCharge(eg2.getAtom(j).getFormalCharge());
			// Add aromaticity flag
			if (eg2.getAtom(j).getAttribute("aromatic") != null) {
				a.setAttribute("aromatic", eg2.getAtom(j).getAttributeValue(
						"aromatic"));
			}
			graftCopolymer.addAtom(a);
		}
		List<CMLBond> eg2bonds = eg2.getBonds();
		for (int j = 0; j < eg2bonds.size(); j++) {
			CMLBond b = new CMLBond();
			b.setAttribute("atomRefs2", eg2bonds.get(j).getAtomId(0) + " "
					+ eg2bonds.get(j).getAtomId(1));
			b.setId(eg2bonds.get(j).getAtomId(0) + " "
					+ eg2bonds.get(j).getAtomId(1));
			b.setOrder("1");
			graftCopolymer.addBond(b);
		}
	}

	/**
	 * Deletes R-R bonds and replaces with neighbouring atom bonds.
	 */
	private void addX1X2BondsDeleteRRBonds() {
		ArrayList<CMLBond> bothRAtoms = new ArrayList<CMLBond>();
		CMLBondArray rests = graftCopolymer.getBondArray();
		PolymerisationUtils.createNewMoleculeJoinedWithoutRRgroups(rests,
				bothRAtoms, graftCopolymer);
	}


	/**
	 * Creates CMLFragment for PMLBasic and PMLIntermediate documents.
	 * 
	 * @return CMLFragment
	 */
	public final CMLFragment getFragment() {
		ArrayList<CMLAtom> eg1R = PolymerisationUtils.getRElements(endGroup1);
		ArrayList<CMLAtom> eg2R = PolymerisationUtils.getRElements(endGroup2);
		ArrayList<CMLMolecule> mainChainMolecules = PolymerisationUtils
				.getRepeatUnitMolecules(degreeOfPolymerisationMainChain,
						mainChain, "MC");
		ArrayList<CMLMolecule> branchChainMolecules = PolymerisationUtils
				.getRepeatUnitMolecules(
						degreeOfPolymerisationMainChain
								* ((PolymerisationUtils.getRElements(mainChain)
										.size() - 2))
								* degreeOfPolymerisationBranchChain,
						branchChain, "BC");
		ArrayList<ArrayList<String>> rgroupMainChainIds = PolymerisationUtils.getRgroupConnectors(mainChainMolecules);
		ArrayList<ArrayList<String>> rgroupBranchChainIds = PolymerisationUtils.getRgroupConnectors(branchChainMolecules);
		// End group 1
		CMLMolecule endGroup1Blank = new CMLMolecule();
		endGroup1Blank.setRef("Eg1");
		CMLFragment endGroup1BlankFragment = new CMLFragment();
		endGroup1BlankFragment.addMolecule(endGroup1Blank);
		CMLFragment endGroup2BlankFragment = new CMLFragment();
		CMLFragment parentFragment = new CMLFragment();
		// Main chain
		ArrayList<CMLMolecule> blankRepeatUnitsMainChain = new ArrayList<CMLMolecule>();
		ArrayList<CMLFragment> blankFragmentsMainChain = new ArrayList<CMLFragment>();
		for (int i = 0; i < mainChainMolecules.size(); i++) {
			CMLMolecule m = new CMLMolecule();
			m.setRef("MC" + Integer.toString(i));
			blankRepeatUnitsMainChain.add(m);
			CMLFragment f = new CMLFragment();
			f.addMolecule(m);
			blankFragmentsMainChain.add(f);
		}
		// Branch chain
		ArrayList<CMLMolecule> blankRepeatUnitsBranchChain = new ArrayList<CMLMolecule>();
		ArrayList<CMLFragment> blankFragmentsBranchChain = new ArrayList<CMLFragment>();
		for (int i = 0; i < branchChainMolecules.size(); i++) {
			CMLMolecule m = new CMLMolecule();
			m.setRef("BC" + Integer.toString(i));
			blankRepeatUnitsBranchChain.add(m);
			CMLFragment f = new CMLFragment();
			f.addMolecule(m);
			blankFragmentsBranchChain.add(f);
		}
		// End group 2
		CMLMolecule endGroup2Blank = new CMLMolecule();
		endGroup2Blank.setRef("Eg2");
		endGroup2BlankFragment.addMolecule(endGroup2Blank);
		// Manually add join between end group 1 and repeatUnit(0)
		CMLJoin endGroup1ToRepeatUnit = new CMLJoin();
		String[] eg1JoinValues = new String[2];
		eg1JoinValues[0] = eg1R.get(0).getId();
		eg1JoinValues[1] = rgroupMainChainIds.get(0).get(0);
		endGroup1ToRepeatUnit.setAtomRefs2(eg1JoinValues);
		String moleculeRefs = "PREVIOUS NEXT";
		endGroup1ToRepeatUnit.processMoleculeRefs2AndAtomRefs2(endGroup1Blank,mainChainMolecules.get(0));
		endGroup1ToRepeatUnit.setMoleculeRefs2(moleculeRefs);
		parentFragment.appendChild(endGroup1BlankFragment);
		parentFragment.addJoin(endGroup1ToRepeatUnit);
		parentFragment.appendChild(blankFragmentsMainChain.get(0));
		// Join up main chain
		if (degreeOfPolymerisationMainChain > 1) {
			for (int i = 0; i < mainChainMolecules.size(); i++) {
				if (i == 0) {
					CMLJoin ru0toru1 = new CMLJoin();
					String[] ru0Ru1 = new String[2];
					ru0Ru1[0] = rgroupMainChainIds.get(0).get(1);
					ru0Ru1[1] = rgroupMainChainIds.get(1).get(0);
					ru0toru1.setAtomRefs2(ru0Ru1);
					ru0toru1.processMoleculeRefs2AndAtomRefs2(
							mainChainMolecules.get(i), mainChainMolecules
									.get(i + 1));
					ru0toru1.setMoleculeRefs2(moleculeRefs);
					parentFragment.addJoin(ru0toru1);
					parentFragment.appendChild(blankFragmentsMainChain.get(i + 1));
				}
				if (i > 0 && i < mainChainMolecules.size() - 1) {
					CMLJoin repeatUnit2RepeatUnit = new CMLJoin();
					repeatUnit2RepeatUnit.setAtomRefs2(rgroupMainChainIds
							.get(i).get(1)
							+ " " + rgroupMainChainIds.get(i + 1).get(0));
					repeatUnit2RepeatUnit.processMoleculeRefs2AndAtomRefs2(
							mainChainMolecules.get(i), mainChainMolecules
									.get(i + 1));
					repeatUnit2RepeatUnit.setMoleculeRefs2(moleculeRefs);
					parentFragment.addJoin(repeatUnit2RepeatUnit);
					parentFragment.appendChild(blankFragmentsMainChain.get(i + 1));
				}
			}
		}
		// Build up the graft chain
		ArrayList<CMLFragment> buildGraftFragments = new ArrayList<CMLFragment>();
		CMLFragment parentGraft = new CMLFragment();
		for (int i = 0; i < branchChainMolecules.size(); i++) {
			if (i % degreeOfPolymerisationBranchChain != 0) {
				CMLFragment frag = new CMLFragment();
				frag.addMolecule(blankRepeatUnitsBranchChain.get(i));
				parentGraft.appendChild(frag);
				buildGraftFragments.add(parentGraft);
				parentGraft = new CMLFragment();
			} else {
				if (i < branchChainMolecules.size() - 1) {
					CMLFragment frag = new CMLFragment();
					frag.addMolecule(blankRepeatUnitsBranchChain.get(i));
					CMLJoin gJoin = new CMLJoin();
					gJoin.setAtomRefs2(rgroupBranchChainIds.get(i).get(1) + " "
							+ rgroupBranchChainIds.get(i + 1).get(0));
					gJoin.processMoleculeRefs2AndAtomRefs2(branchChainMolecules
							.get(i), branchChainMolecules.get(i + 1));
					gJoin.setMoleculeRefs2(moleculeRefs);
					parentGraft.appendChild(frag);
					parentGraft.addJoin(gJoin);
				}
			}
		}
		ArrayList<CMLJoin> mainChain2GraftJoins = new ArrayList<CMLJoin>();
		// Attach graft to main chain
		int rCounter = 0;
		for (int i = 0; i < rgroupMainChainIds.size(); i++) {
			for (int j = 1; j < rgroupMainChainIds.get(0).size() - 1; j++) {
				CMLJoin graftMainChainJoin = new CMLJoin();
				graftMainChainJoin.setAtomRefs2(rgroupMainChainIds.get(i)
						.get(j)
						+ " " + rgroupBranchChainIds.get(rCounter).get(0));
				graftMainChainJoin.processMoleculeRefs2AndAtomRefs2(
						mainChainMolecules.get(i), branchChainMolecules
								.get(rCounter));
				graftMainChainJoin.setMoleculeRefs2(moleculeRefs);
				mainChain2GraftJoins.add(graftMainChainJoin);
				rCounter = rCounter + degreeOfPolymerisationBranchChain;
			}
		}
		for (int i = 0; i < buildGraftFragments.size(); i++) {
			parentFragment.appendChild(buildGraftFragments.get(i));
			parentFragment.addJoin(mainChain2GraftJoins.get(i));
		}
		// Add end group 2
		CMLJoin lastRepeatUnitToEndGroup = new CMLJoin();
		lastRepeatUnitToEndGroup.setAtomRefs2(rgroupMainChainIds.get(
				rgroupMainChainIds.size() - 1).get(1)
				+ " " + eg2R.get(0).getId());
		lastRepeatUnitToEndGroup.processMoleculeRefs2AndAtomRefs2(mainChainMolecules.get(mainChainMolecules.size() - 1),endGroup2Blank);
		lastRepeatUnitToEndGroup.setMoleculeRefs2(moleculeRefs);
		parentFragment.appendChild(blankFragmentsMainChain.get(blankFragmentsMainChain.size() - 1));
		parentFragment.addJoin(lastRepeatUnitToEndGroup);
		parentFragment.appendChild(endGroup2BlankFragment);
		return parentFragment;
	}
}
